package br.ufrj.ad.entidade;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import br.ufrj.ad.As;
import br.ufrj.ad.evento.PeerUpload;

public class Peer {
	
	private static int i;
	private String tostring;
	private int id;
	
	private double tempoChegada;
	private Collection<PeerUpload> uploadsAgendados = new ArrayList<PeerUpload>();
	private boolean[] temBloco;
	private As as;
	
	public Peer(As as) {
		super();
		this.as = as;
		temBloco = new boolean[this.as.parametros.B];
		
		tostring = getClass().getSimpleName() + " " + i;
		id = i;
		i++;
	}

	
	/**
	 * Construtor chamado só quando o peer vira Seed
	 * 
	 * @param peerVirandoSeed
	 */
	protected Peer(Peer peerVirandoSeed) {
		super();
		tostring = getClass().getSimpleName() + " " + peerVirandoSeed.getId();
		this.tempoChegada = peerVirandoSeed.getTempoChegada();
		this.uploadsAgendados = peerVirandoSeed.getUploadsAgendados();
		this.temBloco = peerVirandoSeed.temBloco;
		this.as = peerVirandoSeed.as;
	}

	public double getTempoChegada() {
		return tempoChegada;
	}

	public void setTempoChegada(double tempoChegada) {
		this.tempoChegada = tempoChegada;
	}
	
	public Peer escolheDestinoUpload() {
		List<Peer> peers = new ArrayList<Peer>(as.peers);
		peers.remove(this);//para não upar pra eu mesmo
		Peer escolhido = null;
		if(!peers.isEmpty()){
			escolhido = peers.get(new Random().nextInt(peers.size()));
		}
		return escolhido;
	}

	public int escolheBloco(Peer peerDestino) {
		if(as.parametros.blocoRaroPrimeiro) {
			Collections.sort(as.frequenciaBlocos);
			boolean achouBloco = false;
			int i = 0;
			while( !achouBloco && i < as.frequenciaBlocos.size() ) {
				Bloco b = as.frequenciaBlocos.get(i);
				if( this.temBloco[b.getId()] && !peerDestino.temBloco[b.getId()] ) {
					return b.getId();
				}
				i++;
			}
			return -1;
		} else {
			ArrayList<Integer> blocosPossiveis = new ArrayList<Integer>();
			for(int i = 0; i < this.temBloco.length; i++) {
				if(this.temBloco[i] && !peerDestino.temBloco[i]) {
					blocosPossiveis.add(i);
				}
			}
			if(blocosPossiveis.isEmpty()) {
				return -1;
			}
			return blocosPossiveis.get(new Random().nextInt(blocosPossiveis.size()));
		}
	}
	
	public boolean recebeBloco(int id) {
		if(id < 0) {
			return false;
		} else 
		if(!temBloco[id]) {
			temBloco[id] = true;
			int indice = as.frequenciaBlocos.indexOf(new Bloco(id));
			if(indice < 0) {
				as.frequenciaBlocos.add(new Bloco(id));
			} else {
				as.frequenciaBlocos.get(indice).aumentaFrequencia();
			}
			for (int i = 0; i < temBloco.length; i++) {
				if(!temBloco[i]) {
					return false;
				}
			}
			//virou seed
			return true;
		}
		
		//se ele ja tem o bloco não muda
		return false;
	}
	
	public Collection<PeerUpload> getUploadsAgendados() {
		return uploadsAgendados;
	}

	public void addUploadAgendado(PeerUpload event) {
		uploadsAgendados.add(event);
	}

	public void removeUploadAgendado(PeerUpload peerUpload) {
		uploadsAgendados.remove(peerUpload);
	}
	public boolean temBloco() {
		for (int i = 0; i < temBloco.length; i++) {
			if(temBloco[i]) {
				return true;
			}
		}
		return false;
	}
	@Override
	public String toString() {
		String temp = " [";
		for (int i = 0; i < temBloco.length; i++) {
			temp += temBloco[i]+",";
		}
		temp = temp.substring(0,temp.length()-1)+"]";
		return tostring+temp;
	}


	public List<Peer> sample3Peers() {
		List<Peer> peers = new ArrayList<Peer>(as.peers);
		peers.remove(this);//para não upar pra eu mesmo
		if(!peers.isEmpty()){
			Peer escolhido1 = peers.get(new Random().nextInt(peers.size()));
			peers.remove(escolhido1);
			
			Peer escolhido2 = peers.get(new Random().nextInt(peers.size()));
			peers.remove(escolhido2);
			
			Peer escolhido3 = peers.get(new Random().nextInt(peers.size()));
			peers.remove(escolhido3);
			
			peers.clear();
			
			peers.add(escolhido1);
			peers.add(escolhido2);
			peers.add(escolhido3);
			
			return peers;
		}
		
		return null;
	}


	public void downloadRareMatch(List<Peer> peers3) {
		Peer peer1 = peers3.get(0);
		Peer peer2 = peers3.get(1);
		Peer peer3 = peers3.get(2);
		
		for (int i = 0; i < temBloco.length; i++) {
			int frequencia =  0;
			
			if(this.temBloco[i]) {
				continue;
			}
			
			if(peer1.temBloco[i]) {
				frequencia++;
			}
			if(peer2.temBloco[i]) {
				frequencia++;
			}
			if(peer3.temBloco[i]) {
				frequencia++;
			}
			
			if(frequencia == 1) {
				// achou bloco rare match!
				for (Peer peer : peers3) {
					if(peer.temBloco[i]) {
						this.recebeBloco(i);
						break;
					}
				}
				break;
			}
		}
	}


	public boolean temKmenos1Blocos() {
		int qtd = 0;
		for (int i = 0; i < this.temBloco.length; i++) {
			if(this.temBloco[i]) {
				qtd++;
			}
		}
		
		if(qtd == this.temBloco.length-1) {
			return true;
		}
		
		return false;
	}


	public boolean downloadFaltando1(List<Peer> peers3) {
		Peer peer1 = peers3.get(0);
		Peer peer2 = peers3.get(1);
		Peer peer3 = peers3.get(2);
		
		int blocoNaoTem = 0;
		int freq2blocos = 0;
		
		for (int i = 0; i < this.temBloco.length; i++) {
			if(!this.temBloco[i]) {
				blocoNaoTem = i;
				continue;
			}
			
			int freq = 0;
			if(peer1.temBloco[i]) {
				freq++;
			}
			if(peer2.temBloco[i]) {
				freq++;
			}
			if(peer3.temBloco[i]) {
				freq++;
			}
			
			if(freq >= 2) {
				freq2blocos++;
			}
		}
		
		//Download only if every chunk that the peer  has appears at least twice in the sample
		if(freq2blocos == this.temBloco.length-1) {
			if(peer1.temBloco[blocoNaoTem] || peer2.temBloco[blocoNaoTem] || peer3.temBloco[blocoNaoTem]) {
				this.recebeBloco(blocoNaoTem);
				return true;
			}
		}
		
		return false;
	}

	public void downloadQualquer() {
		Peer p = escolheDestinoUpload();
		int b = p.escolheBloco(this);
		this.recebeBloco(b);
	}


	public int getId() {
		return id;
	}

}
